﻿using System;
using System.IO;

namespace Audio
{
    public class MP3 : IDisposable
    {
        private MemoryStream memory;

        public MP3(string path)
        {
            memory = new MemoryStream();

            byte[] value = new byte[1];
            byte[] buffer = new byte[4];

            int count, length;
            byte[] frame;

            using (var stream = File.OpenRead(path))
            {
                count = stream.Read(buffer, 1, 3);
                if (buffer[1] == 73 && buffer[2] == 68 && buffer[3] == 51)
                {
                    count = stream.Read(buffer, 0, 3);
                    count = stream.Read(buffer, 0, 4);

                    length = buffer[0] << 21 | buffer[1] << 14 | buffer[2] << 7 | buffer[3];

                    frame = new byte[length];

                    count = stream.Read(frame, 0, length);

                    buffer[0] = 0;
                    buffer[1] = 0;
                    buffer[2] = 0;
                    buffer[3] = 0;
                }

                while (stream.Read(value, 0, 1) == 1)
                {
                    buffer[0] = buffer[1];
                    buffer[1] = buffer[2];
                    buffer[2] = buffer[3];

                    buffer[3] = value[0];

                    if (buffer[0] == 255 && (buffer[1] & 224) == 224)
                    {
                        length = FrameLength(buffer) - 4;

                        frame = new byte[length];

                        count = stream.Read(frame, 0, length);
                        if (count != length)
                        {
                            throw new Exception("Frame");
                        }

                        memory.Write(buffer, 0, 4);
                        memory.Write(frame, 0, length);

                        buffer[0] = 0;
                        buffer[1] = 0;
                        buffer[2] = 0;
                        buffer[3] = 0;
                    }
                    else
                    {
                        if (buffer[0] == 84 && buffer[1] == 65 && buffer[2] == 71)
                        {
                            break;
                        }
                    }
                }
            }
        }

        public void Dispose()
        {
            if (memory != null)
            {
                memory.Dispose();
            }
        }

        public void Save(string path)
        {
            using (var stream = File.Open(path, FileMode.Create))
            {
                memory.Position = 0;
                memory.WriteTo(stream);
            }
        }

        private int FrameLength(byte[] buffer)
        {
            FrameHeader header = new FrameHeader(buffer);

            if (header.Version == 1 | header.Layer == 0 | header.BitRate == 0 |
                header.BitRate == 15 | header.Frequency == 3)
            {
                throw new Exception("Header");
            }

            int bitrate = Lookup.BitRate[header.Version][header.Layer][header.BitRate];
            int frequency = Lookup.Frequency[header.Version][header.Frequency];

            if (header.Layer == 1)
            {
                if (header.Version == 3)
                {
                    return ((144000 * bitrate) / frequency) + header.Padding;
                }
                else
                {
                    return ((72000 * bitrate) / frequency) + header.Padding;
                }
            }
            else
            {
                throw new Exception("Layer");
            }
        }
    }

    internal class FrameHeader
    {
        private int version;
        private int layer;
        private int bitrate;
        private int frequency;
        private int padding;

        public FrameHeader(byte[] buffer)
        {
            // AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
            //
            // B = Version
            // C = Layer
            // E = BitRate
            // F = Frequency
            // G = Padding

            version = (buffer[1] & 24) >> 3;
            layer = (buffer[1] & 6) >> 1;
            bitrate = (buffer[2] & 240) >> 4;
            frequency = (buffer[2] & 12) >> 2;
            padding = (buffer[2] & 2) >> 1;
        }

        public int Version
        {
            get { return version; }
        }

        public int Layer
        {
            get { return layer; }
        }

        public int BitRate
        {
            get { return bitrate; }
        }

        public int Frequency
        {
            get { return frequency; }
        }

        public int Padding
        {
            get { return padding; }
        }
    }

    internal static class Lookup
    {
        public static int[][][] BitRate;
        public static int[][] Frequency;

        static Lookup()
        {
            Lookup.BitRate = new int[4][][];

            // Version 2.5
            Lookup.BitRate[0] = new int[4][];
            Lookup.BitRate[0][1] = new int[] // Layer 3
            {
                0,
                8,
                16,
                24,
                32,
                40,
                48,
                56,
                64,
                80,
                96,
                112,
                128,
                144,
                160,
                0
            };
            Lookup.BitRate[0][2] = new int[] // Layer 2
            {
                0,
                8,
                16,
                24,
                32,
                40,
                48,
                56,
                64,
                80,
                96,
                112,
                128,
                144,
                160,
                0
            };
            Lookup.BitRate[0][3] = new int[] // Layer 1
            {
                0,
                32,
                48,
                56,
                64,
                80,
                96,
                112,
                128,
                144,
                160,
                176,
                192,
                224,
                256,
                0
            };

            // Version 2
            Lookup.BitRate[2] = new int[4][];
            Lookup.BitRate[2][1] = new int[] // Layer 3
            {
                0,
                8,
                16,
                24,
                32,
                40,
                48,
                56,
                64,
                80,
                96,
                112,
                128,
                144,
                160,
                0
            };
            Lookup.BitRate[2][2] = new int[] // Layer 2
            {
                0,
                8,
                16,
                24,
                32,
                40,
                48,
                56,
                64,
                80,
                96,
                112,
                128,
                144,
                160,
                0
            };
            Lookup.BitRate[2][3] = new int[] // Layer 1
            {
                0,
                32,
                48,
                56,
                64,
                80,
                96,
                112,
                128,
                144,
                160,
                176,
                192,
                224,
                256,
                0
            };

            // Version 1
            Lookup.BitRate[3] = new int[4][];
            Lookup.BitRate[3][1] = new int[] // Layer 3
            {
                0,
                32,
                40,
                48,
                56,
                64,
                80,
                96,
                112,
                128,
                160,
                192,
                224,
                256,
                320,
                0
            };
            Lookup.BitRate[3][2] = new int[] // Layer 2
            {
                0,
                32,
                48,
                56,
                64,
                80,
                96,
                112,
                128,
                160,
                192,
                224,
                256,
                320,
                384,
                0
            };
            Lookup.BitRate[3][3] = new int[] // Layer 1
            {
                0,
                32,
                64,
                96,
                128,
                160,
                192,
                224,
                256,
                288,
                320,
                352,
                384,
                416,
                448,
                0
            };

            Lookup.Frequency = new int[4][];

            // Version 2.5
            Lookup.Frequency[0] = new int[]
            {
                11025,
                12000,
                8000,
                0
            };

            // Version 2
            Lookup.Frequency[2] = new int[]
            {
                22050,
                24000,
                16000,
                0
            };

            // Version 1
            Lookup.Frequency[3] = new int[]
            {
                44100,
                48000,
                32000,
                0
            };
        }
    }
}
